/*
 * mocp-lib.c
 * 
 * Copyright 2012 Kamil Cukrowski <kamil@dyzio.pl>
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation version 2.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301, USA.
 * 
 * based on Music On Console
 * Thank you Damian Pietras <daper@daper.net>
 * 
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <assert.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <signal.h>
#include <getopt.h>
#include <stdarg.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#include <fcntl.h>
#include "mocp-lib.h"

#define MY_NAME "msgmocstat"
#define PINFO(...) printf( __VA_ARGS__ );
#define PDEBUG(str, ...) PDEBUGL(1, str, ##__VA_ARGS__);
#define PDEBUGL(x, str, ...) do{ if ( debug >= x ) _INTERNAL_LOGIT(str, ##__VA_ARGS__); }while(0)
#define PERROR(str, ...) do { printf(MY_NAME ": (%s:%d): ", __FILE__, __LINE__); printf(str, ##__VA_ARGS__); printf("error %d:%s \n", errno, strerror(errno)); }while(0)
#define _INTERNAL_LOGIT(str, ...) do { printf( MY_NAME ": (%s:%d): ", __FILE__, __LINE__); printf(str, ##__VA_ARGS__); printf("\n"); }while(0)
#define PERR PERROR
#define FATAL(cmt, ...) do{ PERROR(cmt,  ##__VA_ARGS__); exit(-1); } while(0)

#define fatal FATAL
#define logit PINFO
#define logerr PERR

extern int debug;

/* its like snprintf, but does not write '\0' on the end of the string (and misses some checking) */
int mysnprintf (char *s, int n, const char *format, ...) {
	va_list va;
	short int i;
	char msg[256];
	
	va_start (va, format);
	vsnprintf (msg, sizeof(msg), format, va);
	msg[sizeof(msg) - 1] = 0;
	va_end (va);
	
	for (i=0; i < n && i < 256 && i < strlen(msg); ++i) {
		s[i] = msg[i];
	}
	return(i);
}

char *xstrdup (const char *s) {
	char *n;
	if (!s) 
		return(NULL);
	if ( !(n = strdup(s)) )
		FATAL("Can't allocate memory!");
	
	return s ? n : NULL;
}
void *xmalloc (const size_t size) {
	void *p;
	if ((p = malloc(size)) == NULL)
		FATAL("Can't allocate memory!");
	return p;
}

void tags_free (struct file_tags *tags) {
	assert (tags != NULL);
	if (tags->title)
		free (tags->title);
	if (tags->artist)
		free (tags->artist);
	if (tags->album)
		free (tags->album);
	free (tags);
}

void file_info_reset (struct file_info *f) {
	f->file = NULL;
	f->title = NULL;
	f->bitrate = -1;
	f->rate = -1;
	f->curr_time = -1;
	f->total_time = -1;
	f->channels = 1;
	f->state = STATE_STOP;
} 

void event_queue_init (struct event_queue *q) {
	assert (q != NULL);
	
	q->head = NULL;
	q->tail = NULL;
}

/* Push an event on the queue if it's not already there. */
void event_push (struct event_queue *q, const int event, void *data) {
	assert (q != NULL);
	
	if (!q->head) {
		q->head = (struct event *)xmalloc (sizeof(struct event));
		q->head->next = NULL;
		q->head->type = event;
		q->head->data = data;
		q->tail = q->head;
	}
	else {
		assert (q->head != NULL);
		assert (q->tail != NULL);
		assert (q->tail->next == NULL);
	
		q->tail->next = (struct event *)xmalloc (
				sizeof(struct event));
		q->tail = q->tail->next;
		q->tail->next = NULL;
		q->tail->type = event;
		q->tail->data = data;
	}
}

 /* Remove the first event from the queue (don't free the data field). */
void event_pop (struct event_queue *q) {
	struct event *e;
	
	assert (q != NULL);
	assert (q->head != NULL);
	assert (q->tail != NULL);
	
	e = q->head;
	q->head = e->next;
	free (e);
	
	if (q->tail == e)
		q->tail = NULL; /* the queue is empty */
}

/* Get the pointer to the first item in the queue or NULL if the queue is empty. */
struct event *event_get_first (struct event_queue *q) {
	assert (q != NULL);
	return q->head;
}

/* Free event queue content without the queue structure. */
void event_queue_free (struct event_queue *q) {
	struct event *e;
	assert (q != NULL);
	while ((e = event_get_first(q))) {
		free(e->data);
		event_pop (q);
	}
}

/* Send an integer value to the socket, return == 0 on error */
int send_int (int sock, int i) {
	int res;
	
	res = send (sock, &i, sizeof(int), 0);
	if (res == -1)
		logerr ("send() failed: %s\n", strerror(errno));

	return res == sizeof(int) ? 1 : 0;
}
int send_str (int sock, const char *str) {
	int len = strlen (str);
	
	if (!send_int(sock, strlen(str))) {
		logerr("sned_int failed");
		return 0;
	}
	
	if (send(sock, str, len, 0) != len) {
		logerr("send failed");
		return 0;
	}
	return 1;
}

/* Get an intiger value from the socket, return == 0 on error. */
int get_int (int sock, int *i) {
	int res;
	res = recv (sock, i, sizeof(int), 0);
	if (res == -1) {
		logerr ("recv() failed when getting int: %s \n", strerror(errno));
	}
	return res == sizeof(int) ? 1 : 0;
}
/* Get the string from socket, return NULL on error. The memory is malloced. */
char *get_str (int sock) {
	int len;
	int res, nread = 0;
	char *str;
	
	if (!get_int(sock, &len))
		return NULL;

	str = (char *)xmalloc (sizeof(char) * (len + 1));
	while (nread < len) {
		res = recv (sock, str + nread, len - nread, 0);
		if (res == -1) {
			logerr ("recv() failed when getting string: %s", strerror(errno));
			free (str);
			return NULL;
		}
		if (res == 0) {
			logerr ("Unexpected EOF when getting string");
			free (str);
			return NULL;
		}
		nread += res;
	}
	str[len] = 0;

	return str;
}
/* Get an intiger value from the socket without blocking. */
int get_int_noblock (int sock, int *i) {
	int res;
	long flags;
	
	if ((flags = fcntl(sock, F_GETFL)) == -1) {
		logerr  ("fcntl(sock, F_GETFL) failed: ");
		return -1;
	}
	flags |= O_NONBLOCK;
	if (fcntl(sock, F_SETFL, O_NONBLOCK) == -1) {
		logerr  ("Setting O_NONBLOCK for the socket failed: ");
		return -1;
	}
	res = recv (sock, i, sizeof(int), 0);
	flags &= ~O_NONBLOCK;
	if (fcntl(sock, F_SETFL, flags) == -1) {
		logerr ("Restoring flags for socket failed: ");
		return -1;
	}
	
	if (res == sizeof(int))
		return 1;
	if (res < 0 && errno == EAGAIN) {
		logit("Getting event from moc srv would block.");
		return 0;
	}
	logit("Can't receive value from the server.");
	return 0;
}

/* Copy the tags data from src to dst freeing old fields if necessary. */
static void tags_copy (struct file_tags *dst, const struct file_tags *src) {
	if (dst->title)
		free (dst->title);
	dst->title = xstrdup (src->title);
	
	if (dst->artist)
		free (dst->artist);
	dst->artist = xstrdup (src->artist);
	
	if (dst->album)
		free (dst->album);
	dst->album = xstrdup (src->album);
	
	dst->track = src->track;
	dst->time = src->time;
	dst->filled = src->filled;
}
static struct file_tags *tags_new () {
	struct file_tags *tags;
	
	tags = (struct file_tags *)xmalloc (sizeof(struct file_tags));
	tags->title = NULL;
	tags->artist = NULL;
	tags->album = NULL;
	tags->track = -1;
	tags->time = -1;
	tags->filled = 0;
	
	return tags;
}
struct file_tags *tags_dup (const struct file_tags *tags) {
	struct file_tags *dtags;
	
	assert (tags != NULL);
	
	dtags = tags_new();
	tags_copy (dtags, tags);
	
	return dtags;
}

void free_tag_ev_data (struct tag_ev_response *d) {
	assert (d != NULL);
	
	free (d->file);
	tags_free (d->tags);
	free (d);
}
struct file_tags *recv_tags (int sock) {
	struct file_tags *tags = tags_new ();
	
	if (!(tags->title = get_str(sock))) {
		logit ("Error while receiving titile");
		tags_free (tags);
		return NULL;
	}
	
	if (!(tags->artist = get_str(sock))) {
		logit ("Error while receiving artist");
		tags_free (tags);
		return NULL;
	}
	
	if (!(tags->album = get_str(sock))) {
		logit ("Error while receiving album");
		tags_free (tags);
		return NULL;
	}
	
	if (!get_int(sock, &tags->track)) {
		logit ("Error while receiving ");
		tags_free (tags);
		return NULL;
	}
	
	if (!get_int(sock, &tags->time)) {
		logit ("Error while receiving time");
		tags_free (tags);
		return NULL;
	}
	
	if (!get_int(sock, &tags->filled)) {
		logit ("Error while receiving 'filled'");
		tags_free (tags);
		return NULL;
	}
	/* Set NULL instead of empty tags. */
	if (!tags->title[0]) {
		free (tags->title);
		tags->title = NULL;
	}
	if (!tags->artist[0]) {
		free (tags->artist);
		tags->artist = NULL;
	}
	if (!tags->album[0]) {
		free (tags->album);
		tags->album = NULL;
	}
	
	return tags;
}

/* generate a title from fmt */
#define check_zero(x) if((x) == '\0') fatal("Unexpected end of title expression")
#define if_not_empty(str)       ((str) && (*str) ? (str) : NULL)
static char *title_expn_subs(char fmt, const struct file_tags *tags) {
	static char track[16];
	
	switch (fmt) {
		case 'n':
			if (tags->track != -1) {
				snprintf (track, sizeof(track), "%d", tags->track);
				return track;
			}
			return NULL;
		case 'a':
			return if_not_empty (tags->artist);
		case 'A':
			return if_not_empty (tags->album);
		case 't':
			return if_not_empty (tags->title);
		default:
			logit ("Error parsing format string.");
	}
	return NULL; /* To avoid gcc warning */
}
static void do_title_expn (char *dest, int size, const char *fmt, const struct file_tags *tags) {
	const char *h;
	int free = --size;
	short escape = 0;
	
	dest[0] = 0;
	
	while (free > 0 && *fmt) {
		if (*fmt == '%' && !escape) {
			check_zero(*++fmt);
	
			/* do ternary expansion
			 * format: %(x:true:false)
			 */
			if (*fmt == '(') {
				char separator, expr[256];
				int expr_pos = 0;
	
				check_zero(*++fmt);
				h = title_expn_subs(*fmt, tags);
	
				check_zero(*++fmt);
				separator = *fmt;
	
				check_zero(*++fmt);
	
				if(h) { /* true */
	
					/* Copy the expression */
					while (escape || *fmt != separator) {
						if (expr_pos == sizeof(expr)-2)
							fatal ("nasted trenary expression too long");
						expr[expr_pos++] = *fmt;
						if (*fmt == '\\')
							escape = 1;
						else
							escape = 0;
						check_zero(*++fmt);
					}
					expr[expr_pos] = '\0';
									   /* eat the rest */
					while (escape || *fmt != ')') {
						if (escape)
							escape = 0;
						else if (*fmt == '\\')
							escape = 1;
						check_zero(*++fmt);
					}
				} else { /* false */
	
					/* eat the truth :-) */
					while (escape || *fmt != separator) {
						if (escape)
							escape = 0;
						else if (*fmt == '\\')
							escape = 1;
						check_zero(*++fmt);
					}
	
					check_zero(*++fmt);
	
					/* Copy the expression */
					while (escape || *fmt != ')') {
						if (expr_pos == sizeof(expr)-2)
							fatal ("trenary expression too long");
						expr[expr_pos++] = *fmt;
						if (*fmt == '\\')
							escape = 1;
						else
							escape = 0;
						check_zero(*++fmt);
					}
					expr[expr_pos] = '\0';
				}
	
				do_title_expn((dest + size - free),
					      free, expr, tags);
				free -= strlen(dest + size - free);
			} else {
				h = title_expn_subs(*fmt, tags);
	
				if (h) {
						   strncat(dest, h, free-1);
					free -= strlen (h);
				}
			}
		}
		else if (*fmt == '\\' && !escape)
			escape = 1;
		else {
			dest[size - free] = *fmt;
			dest[size - free + 1] = 0;
			--free;
			escape = 0;
		}
		fmt++;
	}
	
	free = free < 0 ? 0 : free; /* Possible integer overflow? */
	dest[size - free] = '\0';
}
char *moc_build_title (const struct file_tags *tags) {
	char title[512];
	//do_title_expn (title, sizeof(title), "%(n:%n :)%(a:%a - :)%(t:%t:)%(A: \(%A\):)", tags);
	do_title_expn (title, sizeof(title), "%(n:%n :)%(a:%a - :)%(t:%t:)", tags);
	return xstrdup (title);
}

/* Convert time in second to min:sec text format. buff must be 6 chars long. */
void sec_to_min (char *buff, const int seconds) {
	assert (seconds >= 0);
	
	if (seconds < 6000) {
	
		/* the time is less than 99:59 */
		int min, sec;
	
		min = seconds / 60;
		sec = seconds % 60;
	
		snprintf (buff, 6, "%02d:%02d", min, sec);
	}
	else if (seconds < 10000 * 60)
	
		/* the time is less than 9999 minutes */
		snprintf (buff, 6, "%4dm", seconds/60);
	else
		strcpy (buff, "!!!!!");
}

void clear_item_from_srv(int sock) {
	char *buf;
	if (!(buf = get_str(sock)))
		logit ("Error while receiving item file name");
	if (buf[0]) {
		if (!(get_str(sock))) 
			logit ("Error while receiving tags title");
		if (!(recv_tags(sock)))
			logit ("Error while receiving tags");
	if (recv (sock, buf, sizeof(time_t), 0) == -1) 
		logerr ("recv() failed when getting time_t: %s", strerror(errno));
	}
	free(buf);
}
/* connect to moc server */
int moc_server_connect () 
{

	struct sockaddr_un sock_name;
	int sock;

	/* Create a socket */
	if ((sock = socket (PF_LOCAL, SOCK_STREAM, 0)) == -1) {
		logerr("sopcket in cos");
		return -1;
	}

	sock_name.sun_family = AF_LOCAL;
	strcpy (sock_name.sun_path, "/home/users/kamil/.moc/socket2");
	if (connect(sock, (struct sockaddr *)&sock_name, SUN_LEN(&sock_name)) == -1) {
		PDEBUG("connect in cos");
		close (sock);
		return -1;
	}

	return sock;
}

/* Ping the server. return 1 if the server respond with EV_PONG, otherwise 1. */
int ping_moc_server (int sock) 
{
  
	int event;
	
	send_int(sock, CMD_PING); /* ignore errors - the server could have
				     already closed the connection and sent
				     EV_BUSY */
	if (!get_int(sock, &event)) {
		 logerr ("Error when receiving pong response.");
		 return 0;
	 }
	return event == EV_PONG ? 1 : 0;
}

